﻿namespace Minecraft.Imaging
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// This class handles the loading and rendering of a region file.
    /// </summary>
    public class Region : IMapRenderer
    {
        /// <summary>
        /// Stores the fixed size of a sector in the file.
        /// </summary>
        private static int sectorSize = 4096;

        /// <summary>
        /// Stores the filename for the current region
        /// </summary>
        private string filename;

        /// <summary>
        /// Stores the position of the current region, in region coordinates.
        /// </summary>
        private Point position;

        /// <summary>
        /// Stores the number of chunks in this region file
        /// </summary>
        private int chunkCount;

        /// <summary>
        /// Stores which chunks are available
        /// </summary>
        private bool[,] chunksAvailable;

        /// <summary>
        /// Stores the offsets of each chunk
        /// </summary>
        private int[,] offsets;

        /// <summary>
        /// Stores the length of each chunk
        /// </summary>
        private int[,] lengths;

        /// <summary>
        /// Stores when each chunk was last modified
        /// </summary>
        private int[,] lastModified;

        /// <summary>
        /// Initializes a new instance of the Region class from the filename
        /// </summary>
        /// <param name="filename">The filename of the region</param>
        public Region(string filename)
        {
            this.chunksAvailable = new bool[32, 32];
            this.offsets = new int[32, 32];
            this.lengths = new int[32, 32];
            this.lastModified = new int[32, 32];

            this.filename = filename;
            this.ParseFilenameCoordinates(filename);
            this.LoadHeaderInformation();
        }

        /// <summary>
        /// Gets the number of chunks in this region
        /// </summary>
        public int ChunkCount
        {
            get
            {
                return this.chunkCount;
            }
        }

        /// <summary>
        /// Render this region's chunks
        /// </summary>
        /// <param name="settings">The settings to render with</param>
        /// <returns>the rendered image</returns>
        public Image RenderMap(IMapSettings settings)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Render this region's chunks
        /// </summary>
        /// <param name="updateArea">The area in global chunk coordinates to render</param>
        /// <param name="settings">The settings to render with</param>
        /// <returns>the rendered image</returns>
        public Image RenderMapArea(System.Drawing.Rectangle updateArea, IMapSettings settings)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Parses the coordinates for the region from the filename
        /// </summary>
        /// <param name="filename">The filename to get the coordinates out of</param>
        /// <returns>Whether the world has coordinates (if it is valid or not)</returns>
        private bool ParseFilenameCoordinates(string filename)
        {
            string[] parts = filename.Split(".".ToCharArray());
            if (parts.Length == 4)
            {
                // valid region
                this.position = new Point(int.Parse(parts[1].Trim()), int.Parse(parts[2].Trim()));
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// This method will load all the header information from the region file into this class
        /// </summary>
        private void LoadHeaderInformation()
        {
            using (FileStream inFile = new FileStream(this.filename, FileMode.Open))
            {
                using (BinaryReader inReader = new BinaryReader(inFile))
                {
                    this.chunkCount = 0;

                    // Read the offsets and lengths
                    for (int index = 0; index < Region.sectorSize / 4; index++)
                    {
                        int currentX = index % 32;
                        int currentY = (int)Math.Floor((double)index / 32) % 32;
                        byte[] offsetBytes = inReader.ReadBytes(3);
                        byte[] sectorBytes = inReader.ReadBytes(1);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(offsetBytes);
                            Array.Reverse(sectorBytes);
                        }

                        int offset = offsetBytes[0] + (offsetBytes[1] << 8) + (offsetBytes[2] << 16);
                        int length = sectorBytes[0];
                        if (offset != 0)
                        {
                            this.chunksAvailable[currentX, currentY] = true;
                            this.offsets[currentX, currentY] = offset;
                            this.lengths[currentX, currentY] = length;
                            this.chunkCount++;
                        }
                        else
                        {
                            this.chunksAvailable[currentX, currentY] = false;
                        }
                    }

                    // Read the last modified timestamps
                    for (int index = 0; index < sectorSize / 4; index++)
                    {
                        int currentX = index % 32;
                        int currentY = (int)Math.Floor((double)index / 32) % 32;

                        byte[] modificationDate = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(modificationDate);
                        }

                        if (this.chunksAvailable[currentX, currentY] == true)
                        {
                            this.lastModified[currentX, currentY] = BitConverter.ToInt32(modificationDate, 0);
                        }
                    }
                }
            }
        }
    }
}
